#include <string>
#include <iostream>
#include <sstream>

#include "hashtbl.h"
#include "EncodedStructs.h"
#include "AssemblerClasses.h"

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::hex;

vector<unsigned int> encoder(AssemblerData &ASM)
{
	//Declare local variables
	int dummy;
	//Unions, as per main
	INSTU *p;
	INSTU *u;

	//A string for an error message
	string errStr;
	char *str = NULL;
	
	//Decoded Structure
	Decoded d;

	//Hash Tables
	HashTbl *OpTbl, *RegTbl;

	//A pointer to the vector of type class AssemblyCommands for the commands
	vector<AssemblyCommands> *commands = ASM.ret_data();

	//A pointer to a vector of unsigned ints for the machine code
	vector<unsigned int> machineCode = *ASM.ret_mcData();

	//Initialize union u
	u = new INSTU;
	u->instruction = 0;
	//Allocating memory for the hash tables before calling on functions!!!
	if(!(OpTbl=new HashTbl(16, NULL))) 
	{
		fprintf(stderr, "ERROR: hashtbl_create() failed\n");
		exit(EXIT_FAILURE);
	}

	if(!(RegTbl=new HashTbl(16, NULL))) 
	{
		fprintf(stderr, "ERROR: hashtbl_create() failed\n");
		exit(EXIT_FAILURE);
	}

	if( !(str=(char *)malloc(sizeof(char)*10)) )
	{
		printf("ERROR: out of memory. Aborting...\n");
		exit(10);
	}
	

	//Build the hash tables
	HashOpcode(OpTbl);
	HashRegister(RegTbl);

	//This will be the first scan through for the labels.
	//However, this is only the basic idea of it. The final code should look something like this.
	

/*         for(unsigned int j = 0; j < commands->size(); j++)
        {
        
                vector<string> code = ((*commands)[j].ret_code());
                string temporary(code[0]);
        
                cout<<"temporary: "<<temporary<<endl;
        
                char hold[20];
        
                for(unsigned int count = 0; count < temporary.length(); count++)
                {
        
                        if(temporary[count] != ':')
                        {
                                hold[count] = temporary[count];
                        }
                        else
                        {
                                break;
                        }
                }
                printf("the label is: %s\n", hold);
        
                d.opcode = code[1];
        
                d.reg1 = code[2];
                d.reg2 = code[4];
                d.reg3 = code[6];
                
                //This is where we will call the function to insert the Label into HashTable.
         
                dummy=setRegisters(p, d, RegTbl, ASM, i, code);
                labels(p->address, hold, dictionary);
         
                
                
        }
*/


	//Iterate through all the members of the data vector member of ASM
	for(unsigned int i = 0; i < commands->size(); i++ )
	{
		//Initialize a variable for the vector of strings of the command tokens
		vector<string> code = ((*commands)[i].ret_code());
		

		//For commands the first string must be the opcode, 
		//validity will be checked later
		d.opcode = code[0];

		//It works out that for all types of commands operands fall on odd numbers
		d.reg1 = code[1];
		d.reg2 = code[3];
		d.reg3 = code[5];

	//This will convert the labels into their addresses and assign them if they are registers are actually labels.

	/*
		if( (p = (INSTU *)LabelTbl->get(&d.reg1[0])) )
		{
			u->R.rd = p->reg;
		}

		if( (p = (INSTU *)LabelTbl->get(&d.reg2[0])) )
		{
			u->R.rt = p->reg;
		}
		if( (p = (INSTU *)OpTbl->get(&d.opcode[0])) )
		{
			u->R.rs = p-reg;
		}
	*/

		//Get the value of the opcode from the hashtable if it is valid
		if( (p = (INSTU *)OpTbl->get(&d.opcode[0])) )
		{
			//Since we have a valide opcode we can set the registers
			dummy=setRegisters(p, d, RegTbl, ASM, i, code); /*NOTE: Added an argument to send the vector code to check for validity*/
								  /* Need some hel confirming if the format of passing the vector is correct*/

			//With the opcode and the opperands set we must add the instruction to the machine code
			machineCode.push_back( p->instruction);

		}
		else
		{
			//Error: Invalid Opcode
			errStr="Invalid Opcode at Line: ";
			sprintf(str, "%d", (*commands)[i].ret_ln());
			errStr=errStr+str;
			ASM.err_append(errStr);
		}

		
	} 
	free(str);
	delete(OpTbl);
	delete(RegTbl);
	return machineCode;
}

int setRegisters(INSTU *u, Decoded d, HashTbl *tbl, AssemblerData &ASM, int i, vector<string> code)/*NOTE: Same message as found above*/
{
	char *str = NULL;
	numbers *p=NULL;
	numbers *l=NULL; /* adding this in for labels */
	string errStr;
	vector<AssemblyCommands> *commands = ASM.ret_data();

	if( !(str=(char *)malloc(sizeof(char)*10)) )
	{
		printf("ERROR: out of memory. Aborting...\n");
		exit(10);
	}

	/* RTYPE CASING */
	if(u->R.opcode==0)
	{
		/*rd, rt, fd*/
		if(u->R.funct==0 || u->R.funct==2 || u->R.funct==3) 
		{
			if( code[2] == "," && code[4] == ",")
			{		

				p = (numbers *)tbl->get(&d.reg1[0]);
				if(!p)
				{
					//Error
					errStr="Invalid Rd Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.rd = p->reg;
				}
			
				p=(numbers *)tbl->get(&d.reg2[0]);
				if(!p)
				{
					errStr="Invalid Rt Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.rt = p->reg;
				}

				p=(numbers *)tbl->get(&d.reg3[0]);
				if(!p)
				{
					errStr="Invalid Fd Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.fd = p->reg;
				}
				return 3;
				
			}

			else
			{
				sprintf(&errStr[0],"Missing operand or comma!");
				return 0;
			}
		}

		/*rd, rt, rs*/
		else if(u->R.funct==4 || u->R.funct==6 || u->R.funct==7)
		{

			if( code[2] == "," && code[4] == ",")
			{
				p = (numbers *)tbl->get(&d.reg1[0]);
				if(!p)
				{
					errStr="Invalid Rd Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.rd = p->reg;
				}

				p=(numbers *)tbl->get(&d.reg2[0]);
				if(!p)
				{
					errStr="Invalid Rt Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.rt = p->reg;
				}
	
				p=(numbers *)tbl->get(&d.reg3[0]);
				if(!p)
				{
					errStr="Invalid Rs Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.rs = p->reg;
				}
				return 3;
			}

			else
			{
				errStr="Missing operand or comma at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
				return 0;
			}


		}

		/*rd, rs*/
		else if(u->R.funct==9)
		{
			if( code[2] == ",")
			{
				p = (numbers *)tbl->get(&d.reg1[0]);
				if(!p)
				{
					errStr="Invalid Rd Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.rd = p->reg;
				}
				p = (numbers *)tbl->get(&d.reg2[0]);
				if(!p)
				{
					errStr="Invalid Rs Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
			 		u->R.rs = p->reg;
				}
				return 2;
			}

			else
			{
				errStr="Missing operand or comma at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
				return 0;
			}


		}

		/*syscall, break, sync*/
		else if(u->R.funct==12 || u->R.funct==13 || u->R.funct==15)
		{
			return 0;
			/*Not sure about sync. Can't find the format*/
		}
	
		/*rd, rs, rt */
		else if( (u->R.funct >=32 && u->R.funct <= 47) || u->R.funct==10 || u->R.funct==11)
		{
			if( code[2] == "," && code[4] == ",")
			{
				p = (numbers *)tbl->get(&d.reg1[0]);
				if(!p)
				{
					errStr="Invalid Rd Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.rd = p->reg;
				}

				p = (numbers *)tbl->get(&d.reg2[0]);
				if(!p)
				{
					errStr="Invalid Rs Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.rs = p->reg;
				}
				p = (numbers *)tbl->get(&d.reg3[0]);
				if(!p)
				{
					errStr="Invalid Rt Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.rt = p->reg;
				}
				return 3;
			}

			else
			{
				errStr="Missing operand or comma at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
				return 0;
			}

		}

		/*rd*/
		else if(u->R.funct==16 || u->R.funct==18)
		{
			p = (numbers *)tbl->get(&d.reg1[0]);
			if(!p)
			{
				errStr="Invalid Rd Register at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
			}
			else
			{
				u->R.rd = p->reg;
			}
			return 1;
		}

		/*rs*/
		else if(u->R.funct==8 || u->R.funct==17 || u->R.funct==19)
		{
			p = (numbers *)tbl->get(&d.reg1[0]);
			if(!p)
			{
				errStr="Invalid Rs Register at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
			}
			else
			{
				u->R.rs = p->reg;
			}
			return 1;
		}

		/*rs, rt*/
		else if(u->R.funct==24 || u->R.funct==25 || (u->R.funct>=48 && u->R.funct<=54) )
		{

			if( code[2] == ",")
			{
				p = (numbers *)tbl->get(&d.reg1[0]);
				if(!p)
				{
					errStr="Invalid Rs Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.rs = p->reg;
				}

				p = (numbers *)tbl->get(&d.reg2[0]);
				if(!p)
				{
					errStr="Invalid Rt Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.rt = p->reg;
				}
				return 2;
			}

			else
			{
				errStr="Missing operand or comma at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
				return 0;
			}

		}

	}/*End of RTYPE if*/

	/*ITYPE CASES*/

	/*rs, rt, imm*/

	else if( u->R.opcode >= 8 && u->R.opcode <= 14)
	{		
		if( code[2] == "," && code[4] == ",")
		{

			
			p = (numbers *)tbl->get(&d.reg1[0]);
			if(!p)
			{
				errStr="Invalid Rs Register at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
			}
	
			else
			{
				u->R.rs = p->reg;		
			}
			p = (numbers *)tbl->get(&d.reg2[0]);
	
			if(!p)
			{
				errStr="Invalid Rt Register at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
			}

			else
			{
				u->R.rt = p->reg;
			}
	
			/*This is the third operand of the three, the immediate. I'm assuming this is how I would go about taking care of it, basically treating the immediae*/
			/*like a register and setting it right into I.offset since p->reg is just an unsigned int*/ 

			p = (numbers *)tbl->get(&d.reg3[0]);

			if(!p)
			{
				errStr="Invalid Label at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
			}
	
			else
			{
				u->I.offset = p->reg; 
			}
				return 3;

		}	

		else
		{
			errStr="Missing operand or comma at Line: ";
			sprintf(str, "%d", (*commands)[i].ret_ln());
			errStr=errStr+str;
			ASM.err_append(errStr);
			return 0;
		}


	}

	/*rt, imm*/
	else if(u->R.opcode >= 32 && u->R.opcode <= 38 || u->R.opcode == 15 || (u->R.opcode >= 40 && u->R.opcode < 43) || u->R.opcode == 46)
	{
	
		if( code[2] == ",")
		{

			/*Special Base Shift Register Case*/
			if(code[4] == "(" && code[6] == ")")
			{
				p = (numbers *)tbl->get(&d.reg1[0]);

				if(!p)
				{
					errStr="Invalid Rt Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
	
				else
				{
					u->R.rt = p->reg;
		
				}

				p = (numbers *)tbl->get(&d.reg2[0]);

				if(!p)
				{
					errStr="Invalid Offset at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
	
				else
				{
					u->I.offset = p->reg;		
				}

				p = (numbers *)tbl->get(&d.reg3[0]);

				if(!p)
				{
					errStr="Invalid Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
	
				else
				{
					u->I.offset = u->I.offset + p->reg;		
				}

				return 2;
			}
			/*End of Base Shift Case*/			

			else
			{
				p = (numbers *)tbl->get(&d.reg1[0]);

				if(!p)
				{
					errStr="Invalid Rt Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
	
				else
				{
					u->R.rt = p->reg;		
				}
		
	

			/* This is where we need to insert labels. The function has not been created yet to grab the 
		   	labels.
			*/

			/* This is what the code optimally should look like:
	
				p = (numbers *)tbl -> get(&d.reg2[0]);

				if(!p)
       				{

					l = (numbers *)LABELtbl -> get(&d.reg2[0]);
					if(!l)
					{
					
						
               			 sprintf(&errStr[0], "Invalid Label  at Line: %d\n", (*commands)[i].ret_ln());
               			 ASM.err_append(errStr);
        			}
        
       			 	else
        			{
                			u->I.offset = p->reg;
        			}

			*/

				p = (numbers *)tbl->get(&d.reg2[0]);

				if(!p)
				{
					errStr="Invalid Immediate at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
		
				else
				{
					u->I.offset = p->reg;
				}
		
				return 2;
			}
		}
	
		else
		{
			errStr="Missing operand or comma at Line: ";
			sprintf(str, "%d", (*commands)[i].ret_ln());
			errStr=errStr+str;
			ASM.err_append(errStr);
			return 0;
		}
	
	
	}

	/*ft, imm*/
	else if( u->R.opcode == 49 || u->R.opcode == 56 || u->R.opcode == 57 || u->R.opcode == 61)
	{

		if( code[2] == ",")
		{
			p = (numbers *)tbl->get(&d.reg1[0]);

			if(!p)
			{
				errStr="Invalid Ft Register at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
			}
	
			else
			{
				u->R.rt = p->reg;		
			}
	
			p = (numbers *)tbl->get(&d.reg2[0]);

			if(!p)
			{
				errStr="Invalid Immediate at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
			}

			else
			{
				u->I.offset = p->reg;
			}
	
			return 2;
		}

		else
		{
			errStr="Missing operand or comma at Line: ";
			sprintf(str, "%d", (*commands)[i].ret_ln());
			errStr=errStr+str;
			ASM.err_append(errStr);
			return 0;
		}


	}


	/*JTYPE CASES*/
	/*Will add in validity checks when code for this section is completed*/
	else if(u->R.opcode==2 || u->R.opcode==3)
	{
		/*j and jal instructions. Only one operand, it is a target that represents the
		address of an instruction. In order to fit the address into the remaining 
		26 bits of the instruction (the first 6 are occupied by the opcode 0x02 or 
		0x03) we remove the leading 4 bits, which represent the program counter, and
		the trailing "00" that results from the fact that instruction addresses are
		all multiples of four (32-bit->4-byte instructions.) the address itself comes
		from the address given by the .text directive that proceeds the command, plus
		the offset given by 4 * command number where command number is the number of
		commands after the .text directive*/

		/*Get the address of the label and shift left. By putting it into the 26 bit
		union, the leading 6 bits will be truncated automatically.*/
		//u->J.target = getLabelAddress(&d.reg2[0])>>2;
		return 1;
	}/*END OF JTYPE CASES*/

	/*FLOATING POINT REGISTERS*/
	else if(u->R.opcode == 16 || u->R.opcode == 17 || u->R.opcode == 18 || u->R.opcode == 19)
	{
        if( (u->R.funct == 5)  || (u->R.funct == 14) || (u->R.funct == 33) || (u->R.funct == 32) ||
			(u->R.funct == 36) || (u->R.funct == 15) || (u->R.funct == 6)  || (u->R.funct == 7)  || 
			(u->R.funct == 12) || (u->R.funct == 4)  || (u->R.funct == 13) )
		{

		if( code[2] == ",")
		{
			p = (numbers *)tbl->get(&d.reg1[0]);
			if(!p)
			{
				errStr="Invalid Fd Register at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
			}
			else
			{
				u->R.fd = p->reg; 
			}

			p = (numbers *)tbl->get(&d.reg2[0]);
			if(!p)
			{
				errStr="Invalid Rd Register at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
			}
			else
			{
				u->R.rd = p->reg; 
			}
			return 2;
		}

		else
		{
			errStr="Missing operand or comma at Line: ";
			sprintf(str, "%d", (*commands)[i].ret_ln());
			errStr=errStr+str;
			ASM.err_append(errStr);
			return 0;
		}


	    }

		else if(u->R.funct == 0 || u->R.funct == 3 || u->R.funct == 2 || u->R.funct == 1 || u->R.funct == 19 || u->R.funct == 18)
		{
			if( code[2] == "," && code[4] == ",")
			{
				p = (numbers *)tbl->get(&d.reg1[0]);
				if(!p)
				{
					errStr="Invalid Fd Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.fd = p->reg; 
				}
				p = (numbers *)tbl->get(&d.reg2[0]);
				if(!p)
				{
					errStr="Invalid Rd Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.rd = p->reg; 
				}
				p = (numbers *)tbl->get(&d.reg3[0]);
				if(!p)
				{
					errStr="Invalid Rd Register at Line: ";
					sprintf(str, "%d", (*commands)[i].ret_ln());
					errStr=errStr+str;
					ASM.err_append(errStr);
				}
				else
				{
					u->R.rt = p->reg; 
				}
				return 3;
			}

			else
			{
				errStr="Missing operand or comma at Line: ";
				sprintf(str, "%d", (*commands)[i].ret_ln());
				errStr=errStr+str;
				ASM.err_append(errStr);
				return 0;
			}

		}

		else if(u->R.funct == 24)
		{
			return 0;
		}
	}

		
	/* None of the cases! */
	else
	{
		errStr="Unknown error at Line: ";
		sprintf(str, "%d", (*commands)[i].ret_ln());
		errStr=errStr+str;
		ASM.err_append(errStr);
		return 10;
	}
	return 0;
}

int main()
{
	//variable label hash table
	//HashTbl *LblTbl;  --- this will be implemented later
	AssemblerData ASM;
	vector<unsigned int> hexs;
	vector<string> instructs;
	vector<string> instructsFloating;
	vector<string> instructsAND;
	vector<string> errors;
	string str;
	string fname="something";
/*	
	instructs.push_back("add");
	instructs.push_back("$a3");
	instructs.push_back(",");
	instructs.push_back("$a1");
	instructs.push_back(",");
	instructs.push_back("$a2");
	ASM.data_append(instructs, 1, fname);
	
	instructsFloating.push_back("add.s");
	instructsFloating.push_back("$f7");
    instructsFloating.push_back(",");
    instructsFloating.push_back("$f5");
    instructsFloating.push_back(",");
    instructsFloating.push_back("$f6");
	ASM.data_append(instructsFloating, 2, fname);
	
	instructsAND.push_back("and");
    instructsAND.push_back("$s1");
    instructsAND.push_back(",");
    instructsAND.push_back("$s1");
    instructsAND.push_back(",");
	instructsAND.push_back("$zero");
	ASM.data_append(instructsAND, 3, fname);
*/
	cout<<"Enter a test case for opcode...";
	cin>>str;
	instructs.push_back(str);
	cout<<"Enter a register...";
	cin>>str;
	instructs.push_back(str);
	cout<<"Enter a comma or try to mess me up...";
	cin>>str;
	instructs.push_back(str);
	cout<<"Enter a register...";
	cin>>str;
	instructs.push_back(str);
	cout<<"Enter a comma or try to mess me up...";
	cin>>str;
	instructs.push_back(str);
	cout<<"Enter a register...";
	cin>>str;
	instructs.push_back(str);
	ASM.data_append(instructs, 1, fname);
	hexs=encoder(ASM);
	errors=*ASM.ret_err();

/*	cout<<"For the first test case the instruction in decimal is: "<<hexs[0]<<endl;
        cout<<"For the second test case the instruction in decimal is: "<<hexs[1]<<endl;
        cout<<"For the third test case the instruction in decimal is: "<<hexs[2]<<endl<<endl;
*/
       if(hexs.size() && !(errors.size()))
        {
                cout<<"For the user inputted instruction, in decimal: "<<hexs[4]<<endl;
                cout<<"For the user inputted instruction, in hex: "<<hex<<hexs[4]<<endl;
        }                               
	else cout<<"You must have entered invalid code..."<<endl;

        return 0;
}
